import { triggerEffect } from "../reactive-core/effect";
import { createDep } from "./dep";
//

const targetMap = new WeakMap();
let activeEffect = undefined;
let shouldTrack = false;

export function isTracking() {
  return shouldTrack && activeEffect !== undefined;
}

export class ReactiveEffect {
  public deps = []; // set, 记录当前effect关联的effect
  active = true;
  constructor(public fn, public schedule?, public onstop?: () => void) {}

  run() {
    if (!this.active) {
      return this.fn();
    }

    const lastEffect = activeEffect;
    shouldTrack = true;
    activeEffect = this;

    const res = this.fn();

    shouldTrack = false;
    activeEffect = lastEffect;

    return res;
  }

  stop() {
    if (this.active) {
      cleanupEffect(this);
      if (this.onstop) {
        this.onstop();
      }
      this.active = false;
    }
  }
}

// 清除关联的effect
// 清除当前effect上的所有的dep
// 同时也要根据关联的effect去清除掉当前的effect
export function cleanupEffect(effect: ReactiveEffect) {
  // 遍历去查找关联的effect
  effect.deps.forEach((dep) => {
    // 找到关联的effect,然后从关联的effect上清除本effect
    dep.deps.remove(effect);
  });
  effect.deps.length = 0;
}

export function track(target, type, key) {
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }

  let dep = depsMap.get(key);
  if (!dep) {
    dep = createDep(); // set对象
    depsMap.set(key, dep);
  }
  trackEffect(dep);
}

export function trackEffect(dep) {
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect);
    activeEffect.deps.push(activeEffect);
  }
}

export function trigger(target, type, key, newValue, oldValue) {
  const depsMap = targetMap.get(target);
  if (!depsMap) {
    return;
  }
  const dep = depsMap.get(key);
  if (dep) {
    // 找到所有关联的effect, 然后触发它
    triggerEffect(dep);
  }
}

export function triggerEffect(effects) {
  for (const effect of effects) {
    if (effect.schedule) {
      effect.schedule();
    } else {
      effect.run();
    }
  }
}

export function effect(fn, options) {
  const _effect = new ReactiveEffect(fn);

  _effect.run();

  const runner = _effect.run.bind(_effect);

  return runner;
}
